﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// The time signature (also known as meter signature) specifies how many beats 
    /// are in each measure and which note value constitutes one beat.
    /// </summary>
    public class TimeSignatureElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="TimeSignatureElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this time signature.
        /// </para>
        /// </param>
        /// <param name="signatureType">
        /// <para>
        /// Type: <see cref="TimeSignatureType"/>
        /// </para>
        /// <para>
        /// The type of the created time signature (this can only be <see cref="TimeSignatureType.Common"/>
        /// or <see cref="TimeSignatureType.AllaBreve"/>).
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="signatureType"/> is <see cref="TimeSignatureType.Standard"/>.
        /// </exception>
        public TimeSignatureElement(CompositionPart packagePart, ModelElement owner, TimeSignatureType signatureType)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (signatureType == TimeSignatureType.Standard)
                throw new ArgumentException("This constructor can only be used to create Common or AllaBreve time signatures!", "signatureType");

            SignatureType = signatureType;
        }

        /// <summary>
        /// Creates a new <see cref="TimeSignatureElement"/> instance.
        /// </summary>
        /// <remarks>
        /// This constructor automatically sets the <see cref="SignatureType"/> property to <see cref="TimeSignatureType.Standard"/>.
        /// </remarks>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this time signature.
        /// </para>
        /// </param>
        /// <param name="beats">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The value of the <see cref="Beats"/> property.
        /// </para>
        /// </param>
        /// <param name="beatUnit">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The value of the <see cref="BeatUnit"/> property.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The value of <paramref name="beats"/> or <paramref name="beatUnit"/> is not valid.
        /// Please, refer to the documentation of the respective properties for details.
        /// </exception>
        public TimeSignatureElement(CompositionPart packagePart, ModelElement owner, ushort beats, ushort beatUnit)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException();

            Beats = beats;
            BeatUnit = beatUnit;
        }

        /// <summary>
        /// Creates a new <see cref="TimeSignatureElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this time signature.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal TimeSignatureElement(XmlPackagePart packagePart, ModelElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            beatsAttribute = element.Attributes[PackageVocabulary.BeatsAttribute];
            beatUnitAttribute = element.Attributes[PackageVocabulary.BeatUnitAttribute];
            signatureTypeAttribute = element.Attributes[PackageVocabulary.SignatureTypeAttribute];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.TimeSignatureElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            beatsAttribute = document.CreateAttribute(PackageVocabulary.BeatsAttribute);
            beatUnitAttribute = document.CreateAttribute(PackageVocabulary.BeatUnitAttribute);
            signatureTypeAttribute = document.CreateAttribute(PackageVocabulary.SignatureTypeAttribute);

            element.Attributes.Append(beatsAttribute);
            element.Attributes.Append(beatUnitAttribute);
            element.Attributes.Append(signatureTypeAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the number of the beats in the measure.
        /// </summary>
        /// <remarks>
        /// Setting this property will cause the <see cref="SignatureType"/> property
        /// to be set to <see cref="TimeSignatureType.Standard"/>.
        /// </remarks>
        /// <value>
        /// The value is a positive (greater than 0) integer that indicates how many
        /// beats (defined by <see cref="BeatUnit"/> property) there are in a measure.
        /// </value>
        /// <exception cref="ValidationException">
        /// The value is equal to 0.
        /// </exception>
        public ushort Beats
        {
            get { return ushort.Parse(beatsAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("Beats", value);

                SignatureType = TimeSignatureType.Standard;
                beatsAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("Beats");
                RaisePropertyChanged("MaxDuration");
            }
        }

        /// <summary>
        /// Gets or sets the note value that represents one beat.
        /// </summary>
        /// <remarks>
        /// Setting this property will cause the <see cref="SignatureType"/> property
        /// to be set to <see cref="TimeSignatureType.Standard"/>.
        /// </remarks>
        /// <value>
        /// The note value is expressed as a multiple of 128-th beats.
        /// The 128-th beat is the smallest unit that can be used in the model.<br/>
        /// The value has to be from a range [1, 256] where 1 stands for a 128-th note
        /// and 256 stands for a breve (double-whole note).
        /// </value>
        /// <exception cref="ValidationException">
        /// The value is outside the interval [1, 256].
        /// </exception>
        public ushort BeatUnit
        {
            get { return ushort.Parse(beatUnitAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("BeatUnit", value);

                SignatureType = TimeSignatureType.Standard;
                beatUnitAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                
                RaisePropertyChanged("BeatUnit");
                RaisePropertyChanged("MaxDuration");
            }
        }

        /// <summary>
        /// Gets the maximal total duration of symbols in a measure with this meter.
        /// </summary>
        /// <value>
        /// This value is a simple multiple of <see cref="Beats"/> and <see cref="BeatUnit"/>.
        /// </value>
        public ushort MaxDuration
        {
            get { return (ushort)(Beats * BeatUnit); }
        }

        /// <summary>
        /// Gets or sets the type of the signature.
        /// </summary>
        /// <remarks>
        /// Setting this property to <see cref="TimeSignatureType.Common"/> or <see cref="TimeSignatureType.AllaBreve"/>
        /// will modify the <see cref="Beats"/> and <see cref="BeatUnit"/> properties).
        /// </remarks>
        /// <value>
        /// One of the <see cref="SignatureType"/> values.<br/>
        /// <see cref="TimeSignatureType.Common"/> is default.
        /// </value>
        public TimeSignatureType SignatureType
        {
            get { return (TimeSignatureType)Enum.Parse(typeof(TimeSignatureType) ,signatureTypeAttribute.Value); }
            set
            {
                signatureTypeAttribute.Value = value.ToString();
                
                if (value == TimeSignatureType.Common)
                {
                    beatsAttribute.Value = "4";
                    beatUnitAttribute.Value = DurationalSymbolElement.Quarter.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                }
                else if (value == TimeSignatureType.AllaBreve)
                {
                    beatsAttribute.Value = "2";
                    beatUnitAttribute.Value = DurationalSymbolElement.Half.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                }

                RaisePropertyChanged("SignatureType");
                RaisePropertyChanged("Beats");
                RaisePropertyChanged("BeatUnit");
                RaisePropertyChanged("MaxDuration");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// The XML attribute that holds the value of the <see cref="Beats"/> property.
        /// </summary>
        private XmlAttribute beatsAttribute;
        /// <summary>
        /// The XML attribute that holds the value of the <see cref="BeatUnit"/> property.
        /// </summary>
        private XmlAttribute beatUnitAttribute;
        /// <summary>
        /// The XML attribute that holds the value of the <see cref="SignatureType"/> property.
        /// </summary>
        private XmlAttribute signatureTypeAttribute;

        #endregion
    }

    /// <summary>
    /// Defines the type of a time signature.
    /// </summary>
    public enum TimeSignatureType
    {
        /// <summary>
        /// Represents a so called "common time" or "imperfect time".
        /// </summary>
        /// <remarks>
        /// This time signature is represented by a semicircle or "C" and stands for modern 4/4 time.
        /// The symbol is derived from a broken circle used in music notation 
        /// from the 14th through 16th centuries, where a full circle represented 
        /// what today would be written in 3/2 or 3/4 time, and was called tempus perfectum (perfect time).
        /// </remarks>
        Common,
        /// <summary>
        /// Represents an "alla-breve" or coloquially "cut time" or "cut common time".
        /// </summary>
        /// <value>
        /// This time signature is represented by a semicircl or "C" with a vertical line through.
        /// It stands for modern 2/2 time.<br/>
        /// The symbol is a carry-over from the notational practice of late-Medieval and Renaissance music, 
        /// where it signified tempus imperfectum diminutum (diminished imperfect time)—more precisely, 
        /// a doubling of the speed, or proportio dupla, in duple meter.
        /// </value>
        AllaBreve,
        /// <summary>
        /// Represents a standard time signature.
        /// </summary>
        /// <remarks>
        /// The standard notation of the time signature consists of two numerals:
        /// <list type="bullet">
        /// <item><see cref="TimeSignatureElement.BeatUnit"/> indicates the note value which represents one beat</item>
        /// <item><see cref="TimeSignatureElement.Beats"/> indicates how many such beats there are in a measure (bar)</item>
        /// </list>
        /// </remarks>
        Standard
    }
}
